﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using WebNext.Domain.Interfaces;
using WebNext.UI.Models.Entries;
using WebNext.Domain.Entities;
using WebNext.UI.Utils;
using log4net;
using System.Text;
using MyWeb.WebUI.Models;
using WebNext.UI.Properties;
using WebNext.UI.Models.Comments;
using WebNext.Domain;
using WebNext.UI.Models;
using WebNext.UI.Models.Tags;

namespace WebNext.UI.Controllers
{
	public class EntriesController : BaseController
	{
		private IEntriesRepository entriesRepository;
		private ITagsRepository tagsRepository;

		public EntriesController(IEntriesRepository entriesRepository, ITagsRepository tagsRepository)
		{
			this.entriesRepository = entriesRepository;
			this.entriesRepository.CultureId = this.CultureId;

			this.tagsRepository = tagsRepository;
			this.tagsRepository.CultureId = this.CultureId;
		}

		public ActionResult List(MainViewType viewType, int pageNumber, string tag)
		{
			string viewTypeName = viewType == MainViewType.Projects ? Resources.Projects : Resources.Blog;
			
			EntryType entryType = ViewTypeConverter.GetEntryTypeByViewType(viewType);
			int entriesCount = this.entriesRepository.GetAllCount(entryType, tag, !this.IsLogged);

			if (!string.IsNullOrWhiteSpace(tag))
			{
				if (entriesCount == 0)
					this.TempData[Constants.Message] = string.Format(Resources.NoMatchingTagResults, tag, viewTypeName);
				else
					this.TempData[Constants.Message] = string.Format(Resources.TagResultsForSection, tag, viewTypeName, entriesCount);
			}

			FilterConfig filterConfig = new FilterConfig { FilterMode = FilterMode.Tag, Tag = tag };
			EntriesPagingModel pagingModel = new EntriesPagingModel(entriesCount, viewType, pageNumber, filterConfig);
			if (pagingModel.CurrentPage <= pagingModel.TotalPages || pagingModel.TotalPages == 0)
			{
				IEnumerable<Entry> entries = this.entriesRepository
														.GetAll(entryType, pageNumber - 1, Constants.ListPageSize, tag, !this.IsLogged)
														.Select(s => s.ConvertToListEntry()).ToList();
				EntriesViewModel entriesModel = new EntriesViewModel(entries, pagingModel, this.IsLogged);
				entriesModel.FilterConfig = filterConfig;
				return View(entriesModel);
			}
			else
			{
				this.Log.Error(string.Format("Page {0} not found.", pageNumber));
				this.TempData[Constants.Message] = string.Format(Resources.PageOfNumberNotFound, pageNumber);
				return this.RedirectToAction("List");
			}
		}

		public ActionResult View(int entryId)
		{
			Entry entry = this.entriesRepository.GetById(entryId);
			if(entry == null || (!this.IsLogged && !entry.IsPublished))
			{
				this.TempData[Constants.Message] = string.Format(Resources.EntryOfIdNotFound, entryId);
				return this.RedirectToAction("List", new { viewType = MainViewType.Blog, pageNumber = 1 });
			}
			else
			{
				EntryViewModel entryViewModel = new EntryViewModel(entry, this.IsLogged);
				if (this.TempData[Constants.Temp] is CommentViewModel)
				{
					CommentViewModel commentViewModel = this.TempData[Constants.Temp] as CommentViewModel;
					entryViewModel.Comments.Merge(commentViewModel);
				}
				if (this.Session[Constants.EditedCommentId] != null)
				{
					int editedCommentId = Convert.ToInt32(this.Session[Constants.EditedCommentId]);
					CommentViewModel commentViewModel = entryViewModel.Comments.FirstOrDefault(c => c.Id == editedCommentId);
					if (commentViewModel != null)
					{
						commentViewModel.DisplayMode = CommentDisplayMode.Edit;
					}
				}
				return this.View(entryViewModel);
			}
		}

		public ActionResult ArchiveMonthsList(MainViewType viewType, int selectedYear, int selectedMonth)
		{
			EntryType entryType = ViewTypeConverter.GetEntryTypeByViewType(viewType);
			IEnumerable<ArchiveInfo> archives = this.entriesRepository.GetArchives(entryType, !this.IsLogged);
			EntriesArchiveViewModel viewModel = new EntriesArchiveViewModel(viewType, archives, this.IsLogged);
			viewModel.FilterConfig.Month = selectedMonth;
			viewModel.FilterConfig.Year = selectedYear;
			viewModel.FilterConfig.FilterMode = FilterMode.Archive;

			return this.View(viewModel);
		}

		public ActionResult Archive(MainViewType viewType, int pageNumber, int year, int month)
		{
			string viewTypeName = viewType == MainViewType.Projects ? Resources.Projects : Resources.Blog;

			EntryType entryType = ViewTypeConverter.GetEntryTypeByViewType(viewType);
			int entriesCount = this.entriesRepository.GetArchiveEntriesCount(entryType, year, month, !this.IsLogged);
			if (entriesCount == 0)
				this.TempData[Constants.Message] = string.Format(Resources.NoMatchingArchiveResults, HtmlHelpers.GetMonthNameByNumber(month), year, viewTypeName);
			else
				this.TempData[Constants.Message] = string.Format(Resources.ArchiveResultsForSection, HtmlHelpers.GetMonthNameByNumber(month), year, viewTypeName, entriesCount);

			FilterConfig filterConfig = new FilterConfig { FilterMode = FilterMode.Archive, Year = year, Month = month };
			EntriesPagingModel pagingModel = new EntriesPagingModel(entriesCount, viewType, pageNumber, filterConfig);
			if (pagingModel.CurrentPage <= pagingModel.TotalPages || pagingModel.TotalPages == 0)
			{
				IEnumerable<Entry> entries = this.entriesRepository
														.GetArchiveEntries(entryType, pageNumber - 1, Constants.ListPageSize, year, month, !this.IsLogged)
														.Select(s => s.ConvertToListEntry()).ToList();
				EntriesViewModel entriesModel = new EntriesViewModel(entries, pagingModel, this.IsLogged);
				entriesModel.FilterConfig = filterConfig;
				return View("List", entriesModel);
			}
			else
			{
				this.Log.Error(string.Format("Page {0} not found.", pageNumber));
				this.TempData[Constants.Message] = string.Format(Resources.PageOfNumberNotFound, pageNumber);
				return this.RedirectToAction("List");
			}
		}

		[HttpPost]
		public ActionResult SearchList(MainViewType viewType, string searchValue)
		{
			if (string.IsNullOrEmpty(searchValue))
			{
				return this.RedirectToAction("List", new { viewType = viewType, pageNumber = 1 });
			}
			return this.RedirectToAction("SearchList", new { viewType = viewType, pageNumber = 1, searchValue = searchValue });
		}

		public ActionResult SearchList(MainViewType viewType, int pageNumber, string searchValue)
		{
			string viewTypeName = viewType == MainViewType.Projects ? Resources.Projects : Resources.Blog;
			
			EntryType entryType = ViewTypeConverter.GetEntryTypeByViewType(viewType);
			IEnumerable<Entry> entries = this.entriesRepository.GetAll(entryType).ToList();
			IList<Entry> matchingEntries = entries.Where(e => e.ContainsText(searchValue)).Select(e => e.ConvertToListEntry()).ToList();
			if (!this.IsLogged)
			{
				matchingEntries = matchingEntries.Where(e => e.IsPublished).ToList();
			}
			if (!matchingEntries.Any())
			{
				this.TempData[Constants.Message] = string.Format(Resources.NoMatchingSearchResults, searchValue, viewTypeName);
			}
			else
			{
				this.TempData[Constants.Message] = string.Format(Resources.SearchResultsForSection, searchValue, viewTypeName, matchingEntries.Count);
			}
			FilterConfig filterConfig = new FilterConfig { FilterMode = FilterMode.Search, SearchValue = searchValue };
			EntriesPagingModel pagingModel = new EntriesPagingModel(matchingEntries.Count, viewType, pageNumber, filterConfig);
			if (pagingModel.CurrentPage <= pagingModel.TotalPages || pagingModel.TotalPages == 0)
			{
				IEnumerable<Entry> entryPage = matchingEntries.Skip((pageNumber - 1) * Constants.ListPageSize).Take(Constants.ListPageSize);
				EntriesViewModel entriesModel = new EntriesViewModel(entryPage, pagingModel, this.IsLogged);
				entriesModel.FilterConfig = filterConfig;
			   return this.View("List", entriesModel);
			}
			else
			{
			   this.Log.Error(string.Format("Page {0} not found.", pageNumber));
			   this.TempData[Constants.Message] = string.Format(Resources.PageOfNumberNotFound, pageNumber);
			   return this.RedirectToAction("List");
			}
		}

		[HttpPost]
		[Authorize]
		public ActionResult Manage(int entryId, string action, string backAction)
		{
			if (action == "publish")
			{
				return this.Publish(entryId, true, backAction);
			}
			if (action == "unpublish")
			{
				return this.Publish(entryId, false, backAction);
			}
			if (action == "delete")
			{
				return this.Delete(entryId);
			}

			return this.RedirectToAction("List", new { viewType = MainViewType.Blog, pageNumber = 1 });
		}

		private RedirectToRouteResult Publish(int entryId, bool published, string backAction)
		{
			Entry entry = this.entriesRepository.GetById(entryId);
			if (entry == null)
			{
				this.TempData[Constants.Message] = string.Format(Properties.Resources.EntryOfIdNotFound, entryId);
				return this.RedirectToAction("List", new { viewType = MainViewType.Blog, pageNumber = 1 });
			}
			else
			{
				entry.IsPublished = published;
				string messagePart = published ? Properties.Resources.EntryOfTitlePublished : Properties.Resources.EntryOfTitleUnpublished;
				this.TempData[Constants.Message] = string.Format(messagePart, entry.Title);
			}
			if (backAction == "View")
			{
				return this.RedirectToAction("View", new { entryId = entryId });

			}
			MainViewType viewType = ViewTypeConverter.GetViewTypeByEntryType(entry.Type);
			return this.RedirectToAction("List", new { viewType = viewType, pageNumber = 1 });
		}

		private RedirectToRouteResult Delete(int entryId)
		{
			Entry entry = this.entriesRepository.GetById(entryId);
			if (entry == null)
			{
				this.TempData[Constants.Message] = string.Format(Properties.Resources.EntryOfIdNotFound, entryId);
				return this.RedirectToAction("List", new { viewType = MainViewType.Blog, pageNumber = 1 });
			}
			else if (entry.IsPublished)
			{
				this.TempData[Constants.Message] = Resources.CannotDeletePublishedEntry;
				MainViewType viewType = ViewTypeConverter.GetViewTypeByEntryType(entry.Type);
				return this.RedirectToAction("List", new { viewType = viewType, pageNumber = 1 });
			}
			else
			{
				this.entriesRepository.Delete(entry);

				this.TempData[Constants.Message] = string.Format(Properties.Resources.EntryOfTitleDeleted, entry.Title);
				MainViewType viewType = ViewTypeConverter.GetViewTypeByEntryType(entry.Type);
				return this.RedirectToAction("List", new { viewType = viewType, pageNumber = 1 });
			}
		}

		[HttpPost]
		public ActionResult Rank(int entryId, string action, string backAction)
		{
			if (action == "+")
			{
				return this.Rank(entryId, 1, backAction);
			}
			if (action == "-")
			{
				return this.Rank(entryId, -1, backAction);
			}
			return this.RedirectToAction("List", new { viewType = MainViewType.Blog, pageNumber = 1 });
		}

		private RedirectToRouteResult Rank(int entryId, int value, string backAction)
		{
			string entryRankKey = string.Format("EntryRank{0}", entryId);
			Entry entry = this.entriesRepository.GetById(entryId);
			if (entry == null)
			{
				this.TempData[Constants.Message] = string.Format(Properties.Resources.EntryOfIdNotFound, entryId);
				return this.RedirectToAction("List", new { viewType = MainViewType.Blog, pageNumber = 1 });
			}
			else if (this.Request.Cookies[entryRankKey] != null)
			{
				this.TempData[Constants.Message] = string.Format(Properties.Resources.AlreadyRankedEntry, entry.Title);
			}
			else
			{
				entry.Rank += value;
				HttpCookie cookie = new HttpCookie(entryRankKey, value.ToString());
				cookie.Expires = DateTime.Now.AddYears(5);
				this.HttpContext.Response.Cookies.Set(cookie);
				this.TempData[Constants.Message] = string.Format(Properties.Resources.EntryRankSaved, entry.Title);
			}

			if (backAction == "View")
			{
				return this.RedirectToAction("View", new { entryId = entryId });

			}
			MainViewType viewType = ViewTypeConverter.GetViewTypeByEntryType(entry.Type);
			return this.RedirectToAction("List", new { viewType = viewType, pageNumber = 1 });
		}

		[HttpPost]
		public JsonResult RankAjax(int entryId, int value)
		{
			System.Threading.Thread.Sleep(2000);
			JsonResult result = new JsonResult();

			string entryRankKey = string.Format("EntryRank{0}", entryId);
			Entry entry = this.entriesRepository.GetById(entryId);
			if (entry == null)
			{
				result.Data = new { result = false, message = string.Format(Properties.Resources.EntryOfIdNotFound, entryId) };
			}
			else if (this.Request.Cookies[entryRankKey] != null)
			{
			   result.Data = new { result = false, message = string.Format(Properties.Resources.AlreadyRankedEntry, entry.Title) };
			}
			else
			{
			   entry.Rank += value;
				result.Data = new { result = true, message = this.TempData[Constants.Message] = string.Format(Properties.Resources.EntryRankSaved, entry.Title), currentRank = entry.Rank };
			}
			return result;
		}

		[Authorize]
		public ActionResult New(MainViewType viewType)
		{
			EntryEditViewModel entryEditViewModel = new EntryEditViewModel(0, viewType, string.Empty, this.IsLogged);
			entryEditViewModel.LoadAllTags(this.tagsRepository.GetAll());
			return this.View("Edit", entryEditViewModel);
		}

		[HttpPost]
		[ValidateInput(false)]
		[Authorize]
		public ActionResult New(EntryEditViewModel entryEditViewModel, EntryEditAction action)
		{
			return this.PerformEditAction(entryEditViewModel, action);
		}

		[Authorize]
		public ActionResult Edit(int entryId)
		{
			Entry entry = this.entriesRepository.GetById(entryId);
			if (entry == null)
			{
				this.TempData[Constants.Message] = string.Format(Resources.EntryOfIdNotFound, entryId);
				return this.RedirectToAction("List", new { viewType = MainViewType.Blog, pageNumber = 1 });
			}
			else if (entry.IsPublished)
			{
				this.TempData[Constants.Message] = Resources.CannotEditPublishedEntry;
				MainViewType viewType = ViewTypeConverter.GetViewTypeByEntryType(entry.Type);
				return this.RedirectToAction("List", new { viewType = viewType, pageNumber = 1 });
			}
			else
			{
				IEnumerable<Tag> allTags = this.tagsRepository.GetAll();
				EntryEditViewModel entryEditViewModel = new EntryEditViewModel(entry, allTags, this.IsLogged);
				entryEditViewModel.LoadAllTags(this.tagsRepository.GetAll());
				return this.View(entryEditViewModel);
			}
		}

		[HttpPost]
		[ValidateInput(false)]
		[Authorize]
		public ActionResult Edit(EntryEditViewModel entryEditViewModel, EntryEditAction action)
		{
			entryEditViewModel.LoadAllTags(this.tagsRepository.GetAll());
			return this.PerformEditAction(entryEditViewModel, action);
		}

		private ActionResult PerformEditAction(EntryEditViewModel entryEditViewModel, EntryEditAction action)
		{
			switch (action.Type)
			{
				case EntryEditActionType.AddElement:
					return this.AddElement(entryEditViewModel, action);

				case EntryEditActionType.UseImage:
					return this.View("Edit", entryEditViewModel);
				
				case EntryEditActionType.DeleteElement:
					return this.DeleteElement(entryEditViewModel, action);

				case EntryEditActionType.Preview:
					return this.Preview(entryEditViewModel);
				
				case EntryEditActionType.Save:
					return this.Save(entryEditViewModel);
				
				case EntryEditActionType.Cancel:
					return this.RedirectToAction("List", new { viewType = entryEditViewModel.ViewType, pageNumber = 1 });

				default:
					throw new NotSupportedException(string.Format("Edit action {0} is not suppoted", action.Type));
			}
		}

		private ActionResult AddElement(EntryEditViewModel entryEditViewModel, EntryEditAction action)
		{
			entryEditViewModel.Elements.Insert(action.Index, EntryElementFactory.CreateEntryElementViewModel(action.Index, action.ElementType));
			entryEditViewModel.UpdateElementsOrder();
			return this.View("Edit", entryEditViewModel);
		}

		private ActionResult DeleteElement(EntryEditViewModel entryEditViewModel, EntryEditAction action)
		{
			if (entryEditViewModel.Elements.Count >= action.Index)
			{
				EntryElementViewModel element = entryEditViewModel.Elements[action.Index - 1];
				entryEditViewModel.Elements.RemoveAt(action.Index - 1);
				entryEditViewModel.UpdateElementsOrder();
			}
			return this.View("Edit", entryEditViewModel);
		}

		private ActionResult Preview(EntryEditViewModel entryEditViewModel)
		{
			ValidationResult validationResult = entryEditViewModel.Validate();
			if (!validationResult.IsValid)
			{
				this.TempData[Constants.Message] = validationResult.GetErrorsMessage();
			}
			else
			{
				entryEditViewModel.PreviewEdit = true;
				entryEditViewModel.CreatedOn = DateTime.Now;
				if (entryEditViewModel.Id != 0)
				{
					entryEditViewModel.LastUpdatedOn = DateTime.Now;
				}
			}
			return this.View("Edit", entryEditViewModel);
		}

		private ActionResult Save(EntryEditViewModel entryEditViewModel)
		{
			ValidationResult validationResult = entryEditViewModel.Validate();
			if (!validationResult.IsValid)
			{
				this.TempData[Constants.Message] = validationResult.GetErrorsMessage();
			}
			else if (entryEditViewModel.Id == 0)
			{
				this.TempData[Constants.Message] = string.Format(Resources.EntryHasBeenSaved, entryEditViewModel.Title);
				Entry newEntry = new Entry(string.Empty);
				newEntry.Type = ViewTypeConverter.GetEntryTypeByViewType(entryEditViewModel.ViewType);
				newEntry.CultureId = this.CultureId;
				this.UpdateEntry(newEntry, entryEditViewModel);
				this.entriesRepository.Save(newEntry);
				entryEditViewModel.Id = newEntry.Id;
			}
			else
			{
				Entry entry = this.entriesRepository.GetById(entryEditViewModel.Id);
				if (entry == null)
				{
					this.TempData[Constants.Message] = string.Format(Resources.EntryOfIdNotFound, entryEditViewModel.Id);
					return this.RedirectToAction("List", new { viewType = MainViewType.Blog, pageNumber = 1 });
				}
				else
				{
					this.UpdateEntry(entry, entryEditViewModel);
					this.TempData[Constants.Message] = string.Format(Resources.EntryHasBeenSaved, entryEditViewModel.Title);
				}
			}
			return this.View("Edit", entryEditViewModel);
		}

		private void UpdateEntry(Entry entry, EntryEditViewModel entryEditViewModel)
		{
			entry.Title = entryEditViewModel.Title;
			entry.Content = entryEditViewModel.Elements.OrderBy(e => e.Order).Select(e => e.GetEntryElement()).ToList();

			for (int i = entry.Tags.Count - 1; i >= 0; i--)
			{
				if (!entryEditViewModel.Tags.Any(t => t.Id == entry.Tags[i].Id))
				{
					entry.Tags.RemoveAt(i);
				}
			}

			IEnumerable<Tag> existingTags = this.tagsRepository.GetAll();
			foreach (TagViewModel tagModel in entryEditViewModel.Tags)
			{
				Tag existingTag = existingTags.FirstOrDefault(t => t.Id == tagModel.Id);
				if(existingTag != null && !entry.Tags.Any(t => t.Id == existingTag.Id))
				{
					entry.Tags.Add(existingTag);
				}
			}
		}
	}
}